﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace SecTool.Core.Crypto
{
    public class Service
    {
        /// <summary>
        /// Decrypts document
        /// </summary>
        /// <param name="document"></param>
        /// <returns>Document with decrypted payload</returns>
        public static Document Decrypt(Document document)
        {
            Document d = new Document(document);
            var rsa = KeyProvider.GetRSA(document.Recipient);
            // Create an AesCryptoServiceProvider object
            // with the specified key and IV.
            using (System.Security.Cryptography.SymmetricAlgorithm symAlg
                = GetSymmetricAlgorithm(document.Algorithm))
            {
                symAlg.Key = rsa.Decrypt(document.Key, true);
                symAlg.IV = rsa.Decrypt(document.IV, true);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = symAlg.CreateDecryptor(symAlg.Key, symAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(d.Payload))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        //using (StreamReader srDecrypt = new StreamReader(csDecrypt, System.Text.Encoding.UTF8))
                        //{
                        //    // Read the decrypted bytes from the decrypting stream
                        //    // and place them in a string.
                        //    d.Payload = srDecrypt.CurrentEncoding.GetBytes(srDecrypt.ReadToEnd());
                        //}
                        using(BinaryReader br = new BinaryReader(csDecrypt)){
                            d.Payload = br.ReadBytes(d.Payload.Length);
                        }
                        //using (MemoryStream msWrite = new MemoryStream())
                        //{
                        //    while (csDecrypt.CanRead)
                        //    {
                        //        msWrite.WriteByte((byte)csDecrypt.ReadByte());
                        //    }
                        //    d.Payload = msWrite.ToArray();
                        //}
                    }
                }
            }

            d.Key = null;
            d.IV = null;
            return d;
        }

        /// <summary>
        /// Encrypts document payload using symmetric algorithm and encrypts key using
        /// recipient's public key.
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static Document Encrypt(Document document)
        {
            Document e = new Document(document);
            var rsa = KeyProvider.GetRSA(document.Recipient);
            var symAlg = GetSymmetricAlgorithm(document.Algorithm);

            symAlg.GenerateIV();
            symAlg.GenerateKey();
            ICryptoTransform encryptor = symAlg.CreateEncryptor();

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (BinaryWriter swEncrypt = new BinaryWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(document.Payload);
                    }
                    e.Payload = msEncrypt.ToArray();
                }
            }

            e.IV = rsa.Encrypt(symAlg.IV, true);
            e.Key = rsa.Encrypt(symAlg.Key, true);

            return e;
        }

        /// <summary>
        /// Signs document using Author's private key
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static Document Sign(Document document)
        {
            Document result = new Document(document);
            var rsa = KeyProvider.GetRSA(document.Author);
            result.Signature = rsa.SignData(document.Payload, GetHashAlgorithm(document.HashAlgorithm));
            return result;
        }

        /// <summary>
        /// Verifies document signature using Author's public key
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static bool Verify(Document document)
        {
            var rsa = KeyProvider.GetRSA(document.Author);
            return rsa.VerifyData(document.Payload, GetHashAlgorithm(document.HashAlgorithm), document.Signature);
        }

        /// <summary>
        /// Returns instance of supported hash algorithm
        /// </summary>
        /// <param name="alg"></param>
        /// <returns>Hash alorithm implementation</returns>
        private static System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(HashAlgorithm hash)
        {
            System.Security.Cryptography.HashAlgorithm result = null;
            switch (hash)
            {
                case HashAlgorithm.None:
                    break;
                case HashAlgorithm.MD5:
                    result = new MD5CryptoServiceProvider();
                    break;
                case HashAlgorithm.SHA256:
                    result = new SHA256CryptoServiceProvider();
                    break;
                case HashAlgorithm.SHA512:
                    result = new SHA512CryptoServiceProvider();
                    break;
                default:
                    break;
            }
            return result;
        }

        /// <summary>
        /// Creates instance of supported symmetric algorithm
        /// </summary>
        /// <param name="alg"></param>
        /// <returns>Symmetric algorithm implementation</returns>
        private static System.Security.Cryptography.SymmetricAlgorithm GetSymmetricAlgorithm(SymmetricAlgorithm alg)
        {
            System.Security.Cryptography.SymmetricAlgorithm result = null;
            switch (alg)
            {
                case SymmetricAlgorithm.None:
                    break;
                case SymmetricAlgorithm.AES:
                    result = new AesCryptoServiceProvider();
                    break;
                case SymmetricAlgorithm.TripleDES:
                    result = new TripleDESCryptoServiceProvider();
                    break;
                case SymmetricAlgorithm.DES:
                    result = new DESCryptoServiceProvider();
                    break;
                case SymmetricAlgorithm.RC2:
                    result = new System.Security.Cryptography.RC2CryptoServiceProvider();
                    break;
                default:
                    break;
            }
            result.Padding = PaddingMode.ANSIX923;
            return result;
        }
    }
}